/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.*;
 
public class Evaluator implements TableListener
{
	private class CollisionData
	{ /* Why can't java have structs like any sane language */
		public static final byte COLL_EDGE = 1;
		public static final byte COLL_STAT_BALL = 2;
		public static final byte COLL_MOV_BALL = 3;
		public Vector position;
		public Ball ball;
		public Ball otherBall; /* Only set if type != COLL_EDGE */
		public double vRelative;
		public Vector otherPosition;
		public byte collisionType;
	}

	private LinkedList<CollisionData> collisions;
	private HashMap<Ball, Vector> pottedBalls;
	public static final byte DIRECT_SHOT = 1;
	public static final byte INDIRECT_SHOT = 2;
	public static final byte EDGE_SHOT = 3;
	public static final byte COMBINATION_SHOT = 4;
	public static final byte SAFETY_SHOT = 5;
	public static final byte FOUL = 6;

	private byte shotType;
	private int numCollisions;
	private Table table;

	public Evaluator(Table t)
	{
		collisions = new LinkedList<CollisionData>();
		pottedBalls = new HashMap<Ball, Vector>();
		table = t;
		t.addListener(this);
	}
	public void gameEnd() { }
	public void shotBegin() { }
	public void ballCollision(Ball a, Object o)
	{
		byte type;
		CollisionData c = new CollisionData();
		c.position = a.getPosition().clone();
		if (o instanceof Ball) {
			Ball b = (Ball) o;
			c.otherPosition = b.getPosition().clone();
			c.otherBall = b;
			if (b.getVelocity().magnitude() != 0
					&& a.getVelocity().magnitude() != 0)
				type = CollisionData.COLL_MOV_BALL;
			else
				type = CollisionData.COLL_STAT_BALL;
			c.vRelative = Vector.subtract(a.getVelocity(),
					b.getVelocity())
				.magnitude();
		} else {
			c.vRelative = a.getVelocity().magnitude();
			type = CollisionData.COLL_EDGE;
		}
		c.collisionType = type;
		c.ball = a;
		collisions.add(c);
	}

	public void ballPocketed(Ball b)
	{
		pottedBalls.put(b, b.getPosition().clone());
	}

	private void setShotType(byte newShotType)
	{
		switch (newShotType) {
		case DIRECT_SHOT:
			System.out.println("new Direct");
			break;
		case INDIRECT_SHOT:
			System.out.println("new Indirect");
			break;
		case EDGE_SHOT:
			System.out.println("new Edge");
			break;
		case COMBINATION_SHOT:
			System.out.println("new Combination");
			break;
		}
		if (newShotType == shotType)
			return;
		switch (shotType)
		{
		case 0:
			shotType = newShotType;
			break;
		case DIRECT_SHOT:
			if (newShotType == INDIRECT_SHOT)
				shotType = INDIRECT_SHOT;
			break;
		case INDIRECT_SHOT:
		case EDGE_SHOT:
		case COMBINATION_SHOT:
			if (newShotType != DIRECT_SHOT)
				shotType = COMBINATION_SHOT;
			break;
		default:
			throw new IllegalArgumentException();
		}
	}

	public void shotEnd()
	{
		System.out.println("Shot end");
		if (table.getGameLogic().getPointsLastShot() < 0) {
			System.out.println("Foul");
			shotType = FOUL;
			return;
		}
		if (table.getGameLogic().getPointsLastShot() == 0) {
			System.out.println("Safety");
			shotType = SAFETY_SHOT;
			return;
		}
		shotType = 0;
		HashMap<Ball, Vector> usefulBalls = pottedBalls;
		ListIterator<CollisionData> iter;
		iter = collisions.listIterator(collisions.size());
		while (iter.hasPrevious()) {
			double distance = 0;
			CollisionData c = iter.previous();
			if (c.collisionType == CollisionData.COLL_EDGE) {
				Vector oldPosition = usefulBalls.remove(c.ball);
				if (oldPosition == null)
					continue;
				usefulBalls.put(c.ball, c.position);
				setShotType((c.ball.getValue() == 0)
				            ? INDIRECT_SHOT : EDGE_SHOT);
			} else {
				Vector posA = usefulBalls.remove(c.ball);
				Vector posB = usefulBalls.remove(c.otherBall);
				if (posA == null && posB == null) {
					/* Not a useful collision */
					continue;
				}
				if (c.ball.getValue() == 0
				    || c.otherBall.getValue() == 0)
					setShotType(DIRECT_SHOT);
				else
					setShotType(COMBINATION_SHOT);
				usefulBalls.put(c.ball, c.position);
				usefulBalls.put(c.otherBall, c.otherPosition);
			}
			numCollisions++;
		}
		switch (shotType) {
		case DIRECT_SHOT:
			System.out.println("Direct");
			break;
		case INDIRECT_SHOT:
			System.out.println("Indirect");
			break;
		case EDGE_SHOT:
			System.out.println("Edge");
			break;
		case COMBINATION_SHOT:
			System.out.println("Combination");
			break;
		}
	}

	public int getNumCollisions()
	{
		return numCollisions;
	}

	public byte getShotType()
	{
		return shotType;
	}
}
